/*-------------------------------------------------------------------------
 *
 * scansup.c
 *	  核心词法分析器使用的扫描支持例程
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/parser/scansup.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <ctype.h>

#include "mb/pg_wchar.h"
#include "parser/scansup.h"


/*
 * downcase_truncate_identifier() --- 对未加引号的标识符执行适当的小写和
 * 截断。可选择性地警告截断。
 *
 * 返回一个 palloc 的字符串，包含调整后的标识符。
 *
 * 注意：在某些用法中，传递的字符串不是以 null 结尾的。
 *
 * 注意：该函数的 API 旨在允许进行小写转换，
 * 这可能会增加字符串长度，但是我们尚不支持这一点。
 * 如果您想实现它，您需要修复 utils/adt/varlena.c 中的
 * SplitIdentifierString()。
 */
char * downcase_truncate_identifier(const char *fc_ident, int fc_len, bool fc_warn)
{
	return downcase_identifier(fc_ident, fc_len, fc_warn, true);
}

/*
 * downcase_truncate_identifier 的工作马
 */
char * downcase_identifier(const char *fc_ident, int fc_len, bool fc_warn, bool fc_truncate)
{
	char	   *fc_result;
	int			fc_i;
	bool		fc_enc_is_single_byte;
#ifdef FDD
	bool		fc_is_gb18030 = (GetDatabaseEncoding() == PG_GB18030);
	int			fc_mblen=0;
#endif //FDD

	fc_result = palloc(fc_len + 1);
	fc_enc_is_single_byte = pg_database_encoding_max_length() == 1;

	/*
	 * SQL99 指定了 Unicode 感知的大小写规范化，但我们目前还没有
	 * 相应的基础设施。相反，我们使用 tolower() 提供
	 * 区域感知的转换。然而，有些区域可能对这也不正确（例如，土耳其语可能会
	 * 对 'i' 和 'I' 做奇怪的事情）。我们当前的折衷方案是对高位字符使用
	 * tolower()，只要它们不是多字节字符的一部分，并且对 7 位字符使用
	 * 仅 ASCII 的小写转换。
	 */
	for (fc_i = 0; fc_i < fc_len; fc_i++)
	{
		unsigned char fc_ch = (unsigned char) fc_ident[fc_i];

#ifdef FDD
		if(fc_is_gb18030 && (fc_mblen > 0 || IS_HIGHBIT_SET(fc_ch)))
		{
			if(fc_mblen > 0)
			{
				fc_mblen--;
			}
			else
			{
				fc_mblen = pg_mblen(fc_ident + fc_i)-1;
			}
		}
		else
#endif //FDD
		if (fc_ch >= 'A' && fc_ch <= 'Z')
			fc_ch += 'a' - 'A';
		else if (fc_enc_is_single_byte && IS_HIGHBIT_SET(fc_ch) && isupper(fc_ch))
			fc_ch = tolower(fc_ch);
		fc_result[fc_i] = (char) fc_ch;
	}
	fc_result[fc_i] = '\0';

	if (fc_i >= NAMEDATALEN && fc_truncate)
		truncate_identifier(fc_result, fc_i, fc_warn);

	return fc_result;
}


/*
 * truncate_identifier() --- 将标识符截断为 NAMEDATALEN-1 字节。
 *
 * 如果需要，给定字符串将就地修改。如果请求，将发出警告。
 *
 * 我们要求调用方传递字符串长度，因为这可以在某些常见用法中避免
 * strlen() 调用。
 */
void truncate_identifier(char *fc_ident, int fc_len, bool fc_warn)
{
	if (fc_len >= NAMEDATALEN)
	{
		fc_len = pg_mbcliplen(fc_ident, fc_len, NAMEDATALEN - 1);
		if (fc_warn)
			ereport(NOTICE,
					(errcode(ERRCODE_NAME_TOO_LONG),
					 errmsg("identifier \"%s\" will be truncated to \"%.*s\"",
							fc_ident, fc_len, fc_ident)));
		fc_ident[fc_len] = '\0';
	}
}

/*
 * scanner_isspace() --- 如果 flex 扫描器将字符视为空格，则返回 true
 *
 * 当重要的是匹配词法分析器的行为时，应使用此函数，而不是
 * 可能受区域影响的 isspace() 函数。
 *
 * 从原则上讲，我们可能需要类似的函数用于 isalnum 等，但目前似乎
 * 只需要 isspace。
 */
bool scanner_isspace(char fc_ch)
{
	/* 这必须与 scan.l 的 {space} 字符列表匹配 */
	if (fc_ch == ' ' ||
		fc_ch == '\t' ||
		fc_ch == '\n' ||
		fc_ch == '\r' ||
		fc_ch == '\f')
		return true;
	return false;
}
